1. State

1. State

1.1. Introduction

The State pattern as defined in the GoF book

Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.

Sometimes an object has complex behavior, heavily depending on values of different properties. In general this becomes visible because methods contain lots of if-else or switch-case constructs, and usually using lots of 'flags' (int or boolean fields) just to keep track of what can be done next, and how.

So here is the example: An Order can be manipulated in different ways (add item, confirm, cancel etc.) but we should not be able to do certain things in certain situations., e.g. sending out a canceled order. Order's implementation of the corresponding methods is to delegate to an AbstractOrderState instance. The states themselves know what to do and which state succeeds them.

This class diagram shows the concept:

State Pattern Example - Class Diagram
State Pattern Example - Class Diagram

How the states are interrelated is shown in the statechart diagram.

State Pattern Example - Statechart Diagram
State Pattern Example - Statechart Diagram

I won't pretend the example is a very complex situation. However using state makes it easier when you want to change the complete behavior, e.g. we no longer require payment before sending. We could the add a PayedState after the delivered state. This would require a change in DeliveredState only instead of a multitude of conditional statements.

1.2. Code

1.2.1. Unit Test

First we take a look at some usage scenarios for our Order class, and a simple way to do this is using a unit test.

In the testNormalFlow() we first set up the order (without address or paymentReceived , we would run into some checks in our system). Then we verify the state via a method meant to generate output. Then manipulate the order as intended (add items, confirm, send out, delivered).

Then we try something we know should not be permitted: canceling an order that has already been sent out. We expect an exception to be thrown.

package be.ooxs.examples.designpatterns.state;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.Arrays;

import org.junit.Before;
import org.junit.Test;

public class UTestOrder {
	private Order order;
	private Address address;
	private Item usbCable;
	private Item printPaper;

	@Before
	public void instantiateFixtures() {
		order = new Order();

		address = new Address();
		address.setLines(Arrays.asList("Mr S. Claus", "Northpole 1A", "Arctica"));

		usbCable = createItem("1230010", "USB Cable (5.0m)");
		printPaper = createItem("1230030", "A4 paper (500 p)");
	}

	private Item createItem(String id, String name) {
		Item item = new Item();
		item.setName(name);
		item.setProductId(id);
		return item;
	}

	@Test
	public void testNormalFlow() {
		order.setAddress(address);
		order.setPaymentReceived(true);
		order.addItem(usbCable, 4);
		order.addItem(printPaper, 20);

		assertEquals("Open", order.whatIsTheState());
		order.confirmOrder();
		assertEquals("Ordered", order.whatIsTheState());
		order.orderSendOut("555555X");
		assertEquals("Send", order.whatIsTheState());
		order.orderDelivered();
		assertEquals("Delivered", order.whatIsTheState());
		assertTrue(order.isFinished());
	}

	@Test
	public void disruptedFlow_cancelSend() {
		order.setAddress(address);
		order.setPaymentReceived(true);
		order.addItem(usbCable, 4);

		order.confirmOrder();
		order.orderSendOut("555555X");
		try {
			order.cancel();
			fail("Once an order is sent, it can not be canceled anymore.");
		} catch (RuntimeException exception) {
			assertTrue("Expected exception caught", true);
		}
	}
}

1.2.2. The Base Classes

Now, let's take a look at the most important classes to understand the basic structure of this implementation: Order , Line and Item that are used to model an order and AbstractOrderState as the delegate for state dependant behavior.

package be.ooxs.examples.designpatterns.state;

import java.util.ArrayList;
import java.util.List;

public class Order {
	private AbstractOrderState orderState = new OpenState(this);
	private List<Line> lines = new ArrayList<Line>();
	private Address address;
	private boolean paymentReceived;

	/**
	 * Just for visualizing in test/UI
	 */
	public String whatIsTheState() {
		return orderState.getStateDescription();
	}

	public List<Line> getLines() {
		return lines;
	}

	public void setLines(List<Line> lines) {
		this.lines = lines;
	}

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}

	public boolean isPaymentReceived() {
		return paymentReceived;
	}

	public void setPaymentReceived(boolean paymentReceived) {
		this.paymentReceived = paymentReceived;
	}

	public void addItem(Item item, int quantity) {
		orderState = orderState.addItem(item, quantity);
	}

	public void cancel() {
		orderState = orderState.cancel();
	}

	public void confirmOrder() {
		orderState = orderState.confirmOrder();
	}

	public void orderDelivered() {
		orderState = orderState.orderDelivered();
	}

	public void orderPayed() {
		orderState = orderState.orderPayed();
	}

	public void orderSendOut(String parcelNumber) {
		orderState = orderState.orderSendOut(parcelNumber);
	}

	public boolean isFinished() {
		return orderState.isFinished();
	}

}

Item represents packagings of a product that can be ordered:

package be.ooxs.examples.designpatterns.state;

public class Item {
	private String name;
	private String productId;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getProductId() {
		return productId;
	}

	public void setProductId(String productId) {
		this.productId = productId;
	}
}

A Line is what would appear as a line on an order form: an item and a quantity.

package be.ooxs.examples.designpatterns.state;

public class Line {
	private Item item;
	private int quantity;

	public Line(Item item, int quantity) {
		super();
		this.item = item;
		this.quantity = quantity;
	}

	public Item getItem() {
		return item;
	}

	public int getQuantity() {
		return quantity;
	}

}

Our abstract super class for states implements every method by throwing an exception, subclasses enable these methods by overriding them. You could do this differently, of course...

package be.ooxs.examples.designpatterns.state;

public abstract class AbstractOrderState {
	private Order order;

	public AbstractOrderState(Order order) {
		super();
		this.order = order;
	}

	public Order getOrder() {
		return order;
	}

	protected abstract String getStateDescription();

	public AbstractOrderState addItem(Item item, int quantity) {
		throw new IllegalStateException("Can not add items to an order when the order is " + getStateDescription());
	}

	public AbstractOrderState confirmOrder() {
		throw new IllegalStateException("Can not confirm an order when the order is " + getStateDescription());
	}

	public AbstractOrderState cancel() {
		throw new IllegalStateException("Can not cancel an order when the order is " + getStateDescription());
	}

	public AbstractOrderState orderSendOut(String parcelNumber) {
		throw new IllegalStateException("Can not send an order when the order is " + getStateDescription());
	}

	public AbstractOrderState orderLost() {
		throw new IllegalStateException("Can not lose an order when the order is " + getStateDescription());
	}

	public AbstractOrderState orderFound() {
		throw new IllegalStateException("Can not find a lost order when the order is " + getStateDescription());
	}

	public AbstractOrderState orderDelivered() {
		throw new IllegalStateException("Can not deliver an order when the order is " + getStateDescription());
	}

	public AbstractOrderState orderPayed() {
		throw new IllegalStateException("Can not pay an order when the order is " + getStateDescription());
	}

	public boolean isFinished() {
		return false;
	}
}

1.2.3. State Implementation

Here are the concrete implementations, note that each concrete state determines which state will be it's successor. E.g. after a confirmOrder() we can go to a CanceledState or an OrderedState depending on the internal "state" (or "data") of the Order instance we're working with.

package be.ooxs.examples.designpatterns.state;

public class OpenState extends AbstractOrderState {

	public OpenState(Order order) {
		super(order);
	}

	@Override
	protected String getStateDescription() {
		return "Open";
	}

	@Override
	public AbstractOrderState addItem(Item item, int quantity) {
		getOrder().getLines().add(new Line(item, quantity));
		return this;
	}

	@Override
	public AbstractOrderState confirmOrder() {
		if (getOrder().getLines().isEmpty()) {
			//we could throw an exception instead...
			return new CanceledState(getOrder());
		} else if (getOrder().getAddress() == null) {
			throw new IllegalStateException("An address is needed before the order can be confirmed.");
		}
		return new OrderedState(getOrder());
	}

	@Override
	public AbstractOrderState cancel() {
		return new CanceledState(getOrder());
	}

}

The OrderedState is when the order has been confirmed and our warehouse is in the process of ordering items, assembling the order etc.

package be.ooxs.examples.designpatterns.state;

public class OrderedState extends AbstractOrderState {

	public OrderedState(Order order) {
		super(order);
	}

	@Override
	protected String getStateDescription() {
		return "Ordered";
	}

	@Override
	public AbstractOrderState cancel() {
		return new CanceledState(getOrder());
	}

	@Override
	public AbstractOrderState orderSendOut(String parcelNumber) {
		if (!getOrder().isPaymentReceived()) {
			throw new IllegalStateException("An order should not be send out when payment is not received.");
		}
		return new SendState(getOrder());
	}

}

The SendState is used when our parcels have left our control and are in the good care of a courier or postal service:

package be.ooxs.examples.designpatterns.state;

public class SendState extends AbstractOrderState {

	public SendState(Order order) {
		super(order);
	}

	@Override
	protected String getStateDescription() {
		return "Send";
	}

	@Override
	public AbstractOrderState orderLost() {
		return new LostState(getOrder());
	}

	@Override
	public AbstractOrderState orderDelivered() {
		return new DeliveredState(getOrder());
	}

}

The DeliveredState is when we had confirmation by the courier or postal service that the package has been delivered.

package be.ooxs.examples.designpatterns.state;

public class DeliveredState extends AbstractOrderState {

	public DeliveredState(Order order) {
		super(order);
	}

	@Override
	protected String getStateDescription() {
		return "Delivered";
	}

	public boolean isFinished() {
		return true;
	}
}

The CanceledState can be reached by different paths, e.g. when a customer cancels his order before confirming it.

package be.ooxs.examples.designpatterns.state;

public class CanceledState extends AbstractOrderState {

	public CanceledState(Order order) {
		super(order);
	}

	@Override
	protected String getStateDescription() {
		return "Canceled";
	}

	public boolean isFinished() {
		return true;
	}
}

The LostState is when the courier notifies us of a lost package.

package be.ooxs.examples.designpatterns.state;

public class LostState extends AbstractOrderState {

	public LostState(Order order) {
		super(order);
	}

	@Override
	protected String getStateDescription() {
		return "Lost";
	}

	@Override
	public AbstractOrderState cancel() {
		return new CanceledState(getOrder());
	}

	@Override
	public AbstractOrderState orderDelivered() {
		return new DeliveredState(getOrder());
	}

}